package it.unibo.refolding.pico;

import it.unibo.refolding.common.CommonThings;

import java.awt.Color;
import java.awt.Shape;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.google.common.collect.ImmutableMap;

public final class PlainPicoData extends PicoData {

	public final double scanRateHz;
	final PlainDirectionData approach, retraction;
	public final double rampSizeHardValueV;
	/**
	 * Surface delay.
	 */
	public final double rampDelayS;

	public PlainPicoData(
			double xOffsetM, double springConstantNPerM, double sensZSensMPerV,
			double zScanStartHardValueV, double zLsbToMetresFactor,
			double deflectionLimitV, double deflectionSensitivityMPerV, double scanRateHz,
			PlainDirectionData approach, PlainDirectionData retraction,
			double rampSizeHardValueV, double rampDelayS) {
		super(
				xOffsetM, springConstantNPerM, sensZSensMPerV,
				zScanStartHardValueV, zLsbToMetresFactor, deflectionLimitV, deflectionSensitivityMPerV);
//		Preconditions.checkArgument(retraction == null || approach.deflection.length == retraction.deflection.length);
		this.scanRateHz = scanRateHz;
		this.approach = Objects.requireNonNull(approach, "Both approach and retraction are needed.");
		this.retraction = Objects.requireNonNull(retraction, "Both approach and retraction are needed.");
		this.rampSizeHardValueV = rampSizeHardValueV;
		this.rampDelayS = rampDelayS;
	}
	
	@Override
	public boolean equals(Object other) {
		if (!super.equals(other)) {
			return false;
		}
		PlainPicoData o = (PlainPicoData) other;
        return Double.doubleToLongBits(scanRateHz) == Double.doubleToLongBits(o.scanRateHz)
            && approach.equals(o.approach)
            && retraction.equals(o.retraction)
            & Double.doubleToLongBits(rampSizeHardValueV) == Double
                .doubleToLongBits(o.rampSizeHardValueV) & rampDelayS == o.rampDelayS;
	}
	
	@Override
	public PlainPicoData withSpringConstant(double fixedSpringConstantNPerM) {
		return new PlainPicoData(xOffsetM, fixedSpringConstantNPerM, sensZSensMPerV,
			zScanStartHardValueV, zLsbToMetresFactor, deflectionLimitV, deflectionSensitivityMPerV,
			scanRateHz, approach, retraction, rampSizeHardValueV, rampDelayS);
	}
	
	public double rampSizeMetres() {
		return sensZSensMPerV * rampSizeHardValueV;
	}

	public double[] copyOfRetractionForces() {
		double[] a = new double[retraction.deflection.length];
		for (int i = retraction.deflection.length; --i >= 0; ) {
			a[i] = deflectionLsbToNewtonsFactor() * retraction.deflection[i];
		}
		return a;
	}

	public double actualZRetractionRangeMetres() {
		return zLsbToMetresFactor * retraction.actualZRange();
	}

	@Override
	public PlainDirectionData sharedApproach() {
		return approach;
	}

	public PlainDirectionData sharedRetraction() {
		return retraction;
	}

	/**
	 * -2.346588711284807E-10 < 0
	 * @return normally negative
	 */
	public double actualRetractionStepSizeMetres() {
		return zLsbToMetresFactor * retraction.actualZRange() / (retraction.timeInstantCount() - 1);
	}

	@Override
	public Map<Color, List<Shape>> draw(double svgDefScale) {
		List<Shape> approachShapes = super.drawApproachShapes(svgDefScale);
		List<Shape> retractionShapes = retraction.draw(svgDefScale);
		return ImmutableMap.of(Color.BLUE, approachShapes, Color.RED, retractionShapes);
	}
	
	@Override
	public String toString() {
		return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
	}
	
	@Override
	public short[] minMaxDeflection() {
		short[] approachMinMaxDeflection = approach.minMaxDeflection();
		short[] retractionMinMaxDeflection = retraction.minMaxDeflection();
		return new short[] {
				CommonThings.min(approachMinMaxDeflection[0], retractionMinMaxDeflection[0]),
				CommonThings.max(approachMinMaxDeflection[1], retractionMinMaxDeflection[1])};
	}

	@Override
	public short[] copyOfFullDeflectionArray() {
	  short[] a = Arrays.copyOf(sharedApproach().deflection, sharedApproach().timeInstantCount() + sharedRetraction().timeInstantCount());
	  CommonThings.reverse(a, 0, sharedApproach().timeInstantCount());
	  System.arraycopy(sharedRetraction().deflection, 0, a, sharedApproach().timeInstantCount(), sharedRetraction().timeInstantCount());
	  return a;
	}

	@Override
	public short[] copyOfFullZArray() {
    short[] a = Arrays.copyOf(sharedApproach().z, sharedApproach().timeInstantCount() + sharedRetraction().timeInstantCount());
    CommonThings.reverse(a, 0, sharedApproach().timeInstantCount());
    System.arraycopy(sharedRetraction().z, 0, a, sharedApproach().timeInstantCount(), sharedRetraction().timeInstantCount());
    return a;
	}
	
	public PlainPicoData withZScanStartM(double zScanStartM) {
		double newZScanStartHardValueV = zScanStartM / sensZSensMPerV;
		return withZScanStartHardValueV(newZScanStartHardValueV);
	}
	
	private PlainPicoData withZScanStartHardValueV(double zScanStartHardValueV) {
		return new PlainPicoData(
			xOffsetM, springConstantNPerM, sensZSensMPerV, zScanStartHardValueV,
			zLsbToMetresFactor, deflectionLimitV, deflectionSensitivityMPerV, scanRateHz, approach, retraction,
			rampSizeHardValueV, rampDelayS);
	}

	public PlainPicoData withSurfaceDelayS(double rampDelayS) {
		return new PlainPicoData(
			xOffsetM, springConstantNPerM, sensZSensMPerV, zScanStartHardValueV,
			zLsbToMetresFactor, deflectionLimitV, deflectionSensitivityMPerV, scanRateHz, approach, retraction,
			rampSizeHardValueV, rampDelayS);
	}

    @Override
    public PicoData withDeflectionSensitivity(double fixedDeflectionSensitivityMPerV) {
        return new PlainPicoData(xOffsetM, springConstantNPerM, sensZSensMPerV,
            zScanStartHardValueV, zLsbToMetresFactor, deflectionLimitV, fixedDeflectionSensitivityMPerV,
            scanRateHz, approach, retraction, rampSizeHardValueV, rampDelayS);
    }

    public PlainPicoData withEmptyApproachRetractionAndNoDelay() {
        return new PlainPicoData(xOffsetM, springConstantNPerM, sensZSensMPerV,
            zScanStartHardValueV, zLsbToMetresFactor,
            deflectionLimitV, deflectionSensitivityMPerV, Double.NaN,
            PlainDirectionData.EMPTY, PlainDirectionData.EMPTY,
            Double.NaN, 0);
    }

    public PlainPicoData withRetraction(PlainDirectionData retraction) {
        return new PlainPicoData(xOffsetM, springConstantNPerM, sensZSensMPerV,
            zScanStartHardValueV, zLsbToMetresFactor,
            deflectionLimitV, deflectionSensitivityMPerV, Double.NaN,
            approach, retraction,
            rampSizeHardValueV, rampDelayS);
    }
}
